package com.aliware.tianchi;
import org.apache.dubbo.common.timer.HashedWheelTimer;
import org.apache.dubbo.common.timer.Timeout;
import org.apache.dubbo.common.timer.Timer;
import org.apache.dubbo.common.timer.TimerTask;
import org.apache.dubbo.common.utils.NamedThreadFactory;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;

/**
 * 集群实现
 * 必选接口，核心接口
 * 此类可以修改实现，不可以移动类或者修改包名
 * 选手需要基于此类实现自己的集群调度算法
 *
 * 实现容错策略部分，其他逻辑调用了Directory,Router,LoadBalance等接口实现
 */
public class UserClusterInvoker<T> extends AbstractClusterInvoker<T> {
    private final int retries;
    private final int failbackTasks;
    private volatile Timer failTimer;
    public UserClusterInvoker(Directory<T> directory) {
        super(directory);
        int retriesConfig = 3;
        int failbackTasksConfig = 100;
        this.retries = retriesConfig;
        this.failbackTasks = failbackTasksConfig;
    }

    private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {
        if (this.failTimer == null) {
            synchronized(this) {
                if (this.failTimer == null) {
                    this.failTimer = new HashedWheelTimer(new NamedThreadFactory("failback-cluster-timer", true), 1L, TimeUnit.SECONDS, 32, (long)this.failbackTasks);
                }
            }
        }
        UserClusterInvoker.RetryTimerTask retryTimerTask = new UserClusterInvoker.RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, this.retries, 5L);
        try {
            this.failTimer.newTimeout(retryTimerTask, 5L, TimeUnit.SECONDS);
        } catch (Throwable var7) {
        }

    }

    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        Invoker invoker = null;
        try {
            this.checkInvokers(invokers, invocation);
            invoker = this.select(loadbalance, invocation, invokers, (List)null);
            ProviderInfo providerInfo = GatewayManager.getProviderInfo(invoker);
            if (providerInfo == null) {
                RpcContext.getClientAttachment().setAttachment("timeout", 25);
            } else {
                long timeout = providerInfo.getAvgSpendDuringSecond();
                if (timeout == 0) {
                    RpcContext.getClientAttachment().setAttachment("timeout", 25);
                } else {
                    long act = providerInfo.getAllReqCount().get();
                    long t = providerInfo.getCount().get((int)act / 50);
                    if ( t < 1) {
                        t = timeout;
                    }
                    if (providerInfo.getServerWeight() == 20) {
                        RpcContext.getClientAttachment().setAttachment("timeout", t + 5);
                    } else if (providerInfo.getServerWeight() == 15) {
                        RpcContext.getClientAttachment().setAttachment("timeout", t + 13);
                    } else {
                        RpcContext.getClientAttachment().setAttachment("timeout", t + 16);
                    }
                }

            }
            return this.invokeWithContext(invoker, invocation);
        } catch (Throwable var6 ) {
            this.addFailed(loadbalance, invocation, invokers, invoker);
            return AsyncRpcResult.newDefaultAsyncResult((Object)null, (Throwable)null, invocation);
        }
    }
    public void destroy() {
        super.destroy();
        if (this.failTimer != null) {
            this.failTimer.stop();
        }
    }
    private class RetryTimerTask implements TimerTask {
        private final Invocation invocation;
        private final LoadBalance loadbalance;
        private final List<Invoker<T>> invokers;
        private final int retries;
        private final long tick;
        private Invoker<T> lastInvoker;
        private int retryTimes = 0;
        RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker, int retries, long tick) {
            this.loadbalance = loadbalance;
            this.invocation = invocation;
            this.invokers = invokers;
            this.retries = retries;
            this.tick = tick;
            this.lastInvoker = lastInvoker;
        }
        public void run(Timeout timeout) {
            try {
                Invoker<T> retryInvoker = UserClusterInvoker.this.select(this.loadbalance, this.invocation, this.invokers, Collections.singletonList(this.lastInvoker));
                this.lastInvoker = retryInvoker;
                UserClusterInvoker.this.invokeWithContext(retryInvoker, this.invocation);
            } catch (Throwable var3) {
                if (++this.retryTimes >= this.retries) {
                } else {
                    this.rePut(timeout);
                }
            }
        }
        private void rePut(Timeout timeout) {
            if (timeout != null) {
                Timer timer = timeout.timer();
                if (!timer.isStop() && !timeout.isCancelled()) {
                    timer.newTimeout(timeout.task(), this.tick, TimeUnit.SECONDS);
                }
            }
        }
    }
}

